﻿using System;
using System.Text;
using System.Collections;
using Checkers.Common;

namespace Checkers.View.ConsoleView
{
    /// <summary>
    /// This class responsible to all the display for the user
    /// </summary>
    public class ConsoleDisplay
    {
        // Constants
        private const char sk_BlackPawnMark = 'X';
        private const char sk_BlackKingPawnMark = '*';
        private const char sk_WhitePawnMark = 'O';
        private const char sk_WhiteKingPawnMark = '@';
        private const char sk_EmptyPawnMark = ' ';
        private const char sk_BoardLineSeperator = '=';
        private const char sk_BoardColumnSeperator = '|';
        public const int sk_NumericExitCode = 0;
        public const string sk_ExitCode = "Q";

        /// <summary>
        /// This method prints the board
        /// </summary>
        /// <param name="i_GameBoard">The current board</param>
        public static void PrintBoard(GameBoard i_GameBoard)
        {
            int boardHeight = i_GameBoard.Height;
            int boardWidth = i_GameBoard.Width;
            int currRow;
            int currCol;

            // Print the column names and separators (+2 for the line number)
            Console.Write("   ");
            for (currCol = 1; currCol <= boardWidth; currCol++)
            {
                Console.Write(" {0}  ", (char)(GameBoard.k_BoardStartColumn + currCol - 1));
            }

            Console.WriteLine();
            printLineSeperators(boardWidth);

            // Print the pawn lines
            for (currRow = 0; currRow < boardHeight; currRow++)
            {
                // In each line - print the line num
                Console.Write("{0,-2}{1}", currRow + 1, ConsoleDisplay.sk_BoardColumnSeperator);

                // Print the pawns in the line
                for (currCol = 0; currCol < boardWidth; currCol++)
                {
                    char pawnMark;
                    GamePawn currPawn = i_GameBoard[currCol + 1, currRow + 1];

                    // Get the pawn mark and print it
                    pawnMark = getPawnMark(currPawn);
                    Console.Write(" {0} {1}", pawnMark, ConsoleDisplay.sk_BoardColumnSeperator);
                }

                // Print a line seperation
                Console.WriteLine();
                printLineSeperators(boardWidth);
            }
        }

        /// <summary>
        /// This class prints line seperators
        /// </summary>
        /// <param name="i_LineLength">THe length of the line</param>
        private static void printLineSeperators(int i_LineLength)
        {
            Console.Write("{0}{0}{0}", ConsoleDisplay.sk_BoardLineSeperator);
            for (int charIndex = 1; charIndex <= i_LineLength; charIndex++)
            {
                Console.Write("{0}{0}{0}{0}", ConsoleDisplay.sk_BoardLineSeperator);
            }

            Console.WriteLine();
        }

        /// <summary>
        /// Shows a welcome screen on the console
        /// </summary>
        public static void ShowWelcome()
        {
            Ex02.ConsoleUtils.Screen.Clear();
            Console.WriteLine("Welcome to Checkers Console Game!");
            Console.WriteLine("=================================");
            Console.WriteLine();
            Console.WriteLine(" - Start a new game, or press '{0}' to quit", ConsoleDisplay.sk_ExitCode);
            Console.WriteLine();
        }

        /// <summary>
        /// Shows a goodbye message on the console
        /// </summary>
        public static void ShowGoodbye()
        {
            Console.WriteLine();
            Console.WriteLine("Thank You and Goodbye.");
        }

        /// <summary>
        /// Gets a board size
        /// </summary>
        /// <returns>The board size. Zero if the user exited</returns>
        public static int GetBoardSize(ArrayList i_LegitGameSizes)
        {
            int boardSize;

            // Get the legit game sizes
            StringBuilder gameSizes = new StringBuilder();
            foreach (int legitSize in i_LegitGameSizes)
            {
                gameSizes.Append(string.Format("{0}, ", legitSize));
            }

            gameSizes.Append(ConsoleDisplay.sk_ExitCode);

            // Instruct the user and get the size
            // Stop only in exit code or legit size
            do
            {
                Console.Write(" - Enter the board size ({0}): ", gameSizes.ToString());
                boardSize = getPositiveNumber();
            }
            while (boardSize != ConsoleDisplay.sk_NumericExitCode &&
                !i_LegitGameSizes.Contains(boardSize));

            return boardSize;
        }

        /// <summary>
        /// Gets a valid game type from the console
        /// </summary>
        /// <returns>The given valid game type</returns>
        public static eGameType GetGameType()
        {
            int userInput;

            // Instruct the user and get the size
            // Stop only in legit type
            do
            {
                Console.WriteLine(" - Enter the game type: ");
                Console.WriteLine("     - {0}: {1}", (int)eGameType.HumanVsHuman, eGameType.HumanVsHuman.ToString());
                Console.WriteLine("     - {0}: {1}", (int)eGameType.HumanVsComputer, eGameType.HumanVsComputer.ToString());
                Console.Write("     : ");
                userInput = getPositiveNumber();
            }
            while (userInput != (int)eGameType.HumanVsComputer && userInput != (int)eGameType.HumanVsHuman);

            return (eGameType)userInput;
        }

        /// <summary>
        /// Gets a positive number from the user
        /// </summary>
        /// <returns>The positive number, Zero if given an exit code, Negative if illegal input</returns>
        private static int getPositiveNumber()
        {
            const int v_InvliadReturn = -1;

            string userStringInput = Console.ReadLine();
            int userInput;

            if (userStringInput.ToUpper() == ConsoleDisplay.sk_ExitCode)
            {
                // The user exitet
                userInput = sk_NumericExitCode;
            }
            else if (!int.TryParse(userStringInput, out userInput))
            {
                // Not a valid input (not an integer)
                userInput = v_InvliadReturn;
            }
            else if (userInput <= 0)
            {
                // Negative number
                userInput = v_InvliadReturn;
            }

            return userInput;
        }

        /// <summary>
        /// Shows a current state of the game (Board players and score)
        /// </summary>
        /// <param name="i_Game">The current game data</param>
        public static void ShowGamePlay(GameData i_Game)
        {
            Ex02.ConsoleUtils.Screen.Clear();
            Console.WriteLine("Checkers Game Playing");
            Console.WriteLine("---------------------");
            showPlayers(i_Game);
            Console.WriteLine();
            ConsoleDisplay.PrintBoard(i_Game.GameBoard);
            ConsoleDisplay.ShowCurrentScore(i_Game);
        }

        /// <summary>
        /// Shows the players details on the console
        /// </summary>
        /// <param name="i_Game">The current game data</param>
        private static void showPlayers(GameData i_Game)
        {
            const string v_PlayerMessage = " - {0} Player ({2})\t Score: {1}";
            Console.WriteLine(
                v_PlayerMessage,
                i_Game.Player1.PlayerType.ToString(),
                i_Game.Player1.PlayerScore,
                PawnTypeToPawnMark(i_Game.Player1.PlayerPawnType));
            Console.WriteLine(
                v_PlayerMessage,
                i_Game.Player2.PlayerType.ToString(),
                i_Game.Player2.PlayerScore,
                PawnTypeToPawnMark(i_Game.Player2.PlayerPawnType));
        }

        /// <summary>
        /// Converts a game pawn to a console characterial presentation
        /// </summary>
        /// <param name="i_Pawn">The given game pawn</param>
        /// <returns>The mark of the pawn on the console</returns>
        private static char getPawnMark(GamePawn i_Pawn)
        {
            char pawnMark;

            // If the pawn mark is null - its an empty spot
            // Otherwise - Convert the pawn type to a mark
            if (i_Pawn == null)
            {
                pawnMark = ConsoleDisplay.sk_EmptyPawnMark;
            }
            else if (i_Pawn.Type == eGamePawnType.Black)
            {
                pawnMark = i_Pawn.IsKing ? ConsoleDisplay.sk_BlackKingPawnMark : ConsoleDisplay.sk_BlackPawnMark;
            }
            else
            {
                pawnMark = i_Pawn.IsKing ? ConsoleDisplay.sk_WhiteKingPawnMark : ConsoleDisplay.sk_WhitePawnMark;
            }

            return pawnMark;
        }

        /// <summary>
        /// Converts a pawn type to its matching mark on the console
        /// </summary>
        /// <param name="i_pawnType">The type of the pawn in hand</param>
        /// <returns>The mark of the pawn type</returns>
        private static char PawnTypeToPawnMark(eGamePawnType i_pawnType)
        {
            return i_pawnType == eGamePawnType.Black ? ConsoleDisplay.sk_BlackPawnMark : ConsoleDisplay.sk_WhitePawnMark;
        }

        /// <summary>
        /// Gets a valid move from the player
        /// </summary>
        /// <param name="i_currentPlayer">The player's information</param>
        /// <returns>The players valid move</returns>
        public static GameMove GetMove(GamePlayer i_currentPlayer)
        {
            GameMove userMove = null;

            // Instruct the user
            Console.WriteLine();
            Console.WriteLine(
                "To give cell locations use Column and then Row (i.e. A1), to forfeit the game press '{0}'",
                ConsoleDisplay.sk_ExitCode);
            Console.WriteLine();
            Console.WriteLine(
                "{0} Player ({1}), place your next move: ",
                i_currentPlayer.PlayerPawnType,
                ConsoleDisplay.PawnTypeToPawnMark(i_currentPlayer.PlayerPawnType));
            Console.Write("  - Move pawn from location : ");

            // Get source location
            Location sourceLocation = getLocation();

            // If the user didnt exit
            if (sourceLocation != null)
            {
                Console.Write("  - To location: ");
                Location targetLocation = getLocation();

                if (targetLocation != null)
                {
                    userMove = new GameMove(sourceLocation, targetLocation);
                }
            }

            return userMove;
        }

        /// <summary>
        /// Gets a single valid-formatted location from the user
        /// </summary>
        /// <returns>The location given</returns>
        private static Location getLocation()
        {
            Location resultLocation = null;
            string userInput;
            bool isInputValid = false;

            do
            {
                // Get the input string
                userInput = Console.ReadLine();

                // If its an exit code - let the user exit
                // Otherwise check if the string can possibly contain a legal move
                // (has a column (1 char) index, and a row index (1 or 2 digits)
                if (userInput.ToUpper() == ConsoleDisplay.sk_ExitCode)
                {
                    isInputValid = true;
                }
                else if (userInput.Length >= 2 && userInput.Length <= 3)
                {
                    // Try to convert the first char to a column and the rest to a number
                    char tempColumn;
                    int tempRow;
                    if (char.TryParse(userInput[0].ToString(), out tempColumn) &&
                        int.TryParse(userInput.Substring(1), out tempRow))
                    {
                        // If succeeded - turn into a location
                        resultLocation = new Location(userInput);
                        isInputValid = true;
                    }
                }
                
                // If the input was invalid, inform the user and try again
                if (!isInputValid)
                {
                    Console.WriteLine(
                        "Invalid Location, To give cell locations use Column and then Row (i.e. A1), to forfeit the game press '{0}'",
                        ConsoleDisplay.sk_ExitCode);
                    Console.Write("  - Try again: ");
                }
            }
            while (!isInputValid);

            return resultLocation;
        }

        /// <summary>
        /// Shows an error of an out-of-range move
        /// </summary>
        public static void ShowMoveRangeError()
        {
            Console.WriteLine("Invalid Move: The move you have made is outside of the board.");
        }

        /// <summary>
        /// Shows on error of an illegal move
        /// </summary>
        public static void ShowMoveIllegalError()
        {
            Console.WriteLine("Invalid Move: The move you have made is not legal.");
        }

        /// <summary>
        /// Prompts the user to play again or quit the entire game
        /// </summary>
        /// <returns>True if the user chose to play again</returns>
        public static bool GetPlayAgain()
        {
            const char v_ContinueCode = 'C';
            string userInput;

            Console.WriteLine();
            Console.WriteLine("Would you like to play again?");
            Console.Write(" - Press '{0}' to continue playing or anything else to exit: ", v_ContinueCode);

            // Get the user's input
            do
            {
                userInput = Console.ReadLine();
            }
            while (userInput == string.Empty);

            return userInput.ToUpper() != v_ContinueCode.ToString();
        }

        /// <summary>
        /// Shows an outcome of a single game
        /// </summary>
        /// <param name="i_Game">The information of the game</param>
        /// <param name="i_LastMove">The last move made in the game</param>
        private static void showOutcome(GameData i_Game, GameMove i_LastMove)
        {
            // Print the outcode according to the status
            switch (i_Game.GameStatus)
            {
                case eGameStatus.Tie:
                    showTie();
                    break;
                case eGameStatus.Forfeit:
                    showForfeit(i_Game.CurrentPlayer);
                    break;
                case eGameStatus.Win:
                    showVictory(i_Game, i_LastMove);
                    break;
            }
        }

        /// <summary>
        /// Shows a concluding message of a forfeit game
        /// </summary>
        /// <param name="i_ForfeitPlayer">The player of forfeit the game</param>
        private static void showForfeit(GamePlayer i_ForfeitPlayer)
        {
            Console.WriteLine(
                "{0} {1} Player ({2}) has forfeit the game and lost.",
                i_ForfeitPlayer.PlayerType,
                i_ForfeitPlayer.PlayerPawnType,
                ConsoleDisplay.PawnTypeToPawnMark(i_ForfeitPlayer.PlayerPawnType));
        }

        /// <summary>
        /// Shows a concluding message of a tie game
        /// </summary>
        private static void showTie()
        {
            Console.WriteLine("Game is over with a tie!");
        }

        /// <summary>
        /// Shows a concluding message of a winning game
        /// </summary>
        /// <param name="i_Game">The information of the game</param>
        /// <param name="i_WinningMove">The winning move of the game</param>
        private static void showVictory(GameData i_Game, GameMove i_WinningMove)
        {
            // State the winner
            GamePlayer winnerPlayer = i_Game.WinnerPlayer;

            // Show the victory message
            Console.WriteLine();
            Console.WriteLine(
                "Congratulations! {0} {1} Player ({2}) Wins!",
                winnerPlayer.PlayerType,
                winnerPlayer.PlayerPawnType,
                ConsoleDisplay.PawnTypeToPawnMark(winnerPlayer.PlayerPawnType));
            Console.WriteLine("Victory move was: {0}", i_WinningMove.ToString());
        }

        /// <summary>
        /// Shows a summary message after a single game
        /// </summary>
        /// <param name="i_Game">The information of the game</param>
        /// <param name="i_LastMove">The last move made in the game</param>
        public static void ShowMidSummary(GameData i_Game, GameMove i_LastMove)
        {
            // Print a little space and show the outcome
            Console.WriteLine();
            showOutcome(i_Game, i_LastMove);
            Console.WriteLine();

            // Show the scores
            Console.WriteLine("Game over, The scores are:");
            Console.WriteLine("--------------------------");
            showPlayers(i_Game);
        }

        /// <summary>
        /// Shows a concluding message of the entire game series
        /// </summary>
        /// <param name="i_Game">The information of the game</param>
        public static void ShowTotalSummary(GameData i_Game)
        {
            Ex02.ConsoleUtils.Screen.Clear();
            Console.WriteLine("All games are over, The scores are:");
            Console.WriteLine("-----------------------------------");
            showPlayers(i_Game);

            GamePlayer winningPlayer = getLeadingPlayer(i_Game);
            if (winningPlayer != null)
            {
                Console.WriteLine(
                    "{0} {1} Player ({2}) Wins!",
                    winningPlayer.PlayerType,
                    winningPlayer.PlayerPawnType,
                    ConsoleDisplay.PawnTypeToPawnMark(winningPlayer.PlayerPawnType));
            }
        }

        /// <summary>
        /// Shows the current score of a single game
        /// </summary>
        /// <param name="gameData">The information of the game</param>
        public static void ShowCurrentScore(GameData gameData)
        {
            const string v_ScoreMessage = "{0} Player ({1}): {2} Pawns";

            Console.WriteLine(
                v_ScoreMessage,
                gameData.Player1.PlayerPawnType,
                ConsoleDisplay.PawnTypeToPawnMark(gameData.Player1.PlayerPawnType),
                gameData.GameBoard.BlackPawns);
            Console.WriteLine(
                v_ScoreMessage,
                gameData.Player2.PlayerPawnType,
                ConsoleDisplay.PawnTypeToPawnMark(gameData.Player2.PlayerPawnType),
                gameData.GameBoard.WhitePawns);
        }

        /// <summary>
        /// Gets the player who leads the game series
        /// </summary>
        /// <param name="i_Game">The information of the game</param>
        /// <returns>The player who is leading the game series</returns>
        private static GamePlayer getLeadingPlayer(GameData i_Game)
        {
            // On a tie state - null returns
            GamePlayer leadingPlayer = null;

            if (i_Game.Player1.PlayerScore > i_Game.Player2.PlayerScore)
            {
                // Player 1 leads
                leadingPlayer = i_Game.Player1;
            }
            else if (i_Game.Player2.PlayerScore > i_Game.Player1.PlayerScore)
            {
                // Player 2 leads
                leadingPlayer = i_Game.Player2;
            }

            return leadingPlayer;
        }

        /// <summary>
        /// Shows the last move made in the game
        /// </summary>
        /// <param name="i_LastPlayer">The player who made the last movee</param>
        /// <param name="i_LastMove">The last move made</param>
        /// <param name="i_LastBurntMove">The move which burnt the players pawn. Null when no such move was made</param>
        public static void ShowLastMove(GamePlayer i_LastPlayer, GameMove i_LastMove, GameMove i_LastBurntMove)
        {
            Console.WriteLine(
                "Last Move: {2} Pawn from {0} [{1} Player]",
                i_LastMove,
                i_LastPlayer.PlayerType,
                ConsoleDisplay.PawnTypeToPawnMark(i_LastPlayer.PlayerPawnType));

            // Check if last move was burnt - add it to the output
            // Pause the game to notify the user
            if (i_LastBurntMove != null)
            {
                Console.WriteLine("** Burnt! Should have jumped from {0}", i_LastBurntMove);
                Console.WriteLine("Press Enter to continue...");
                Console.ReadLine();
            }
        }
    }
}